package com.btl.component.web.handler;

import com.btl.boot.core.dto.response.R;
import com.btl.boot.exception.BizException;
import com.btl.boot.exception.SysException;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.nio.file.AccessDeniedException;
import java.sql.SQLException;
import java.util.List;

@Slf4j
@RestControllerAdvice
public class WebExceptionHandler {

    /**
     * 全局异常
     *
     * @return <Void>
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R<Void> handleGlobalException(Exception exception) {
        log.error("全局通用异常信息 ex={}", exception.getMessage(), exception);
        return R.fail("服务器异常");
    }

    /**
     * SQLException Exception
     *
     * @param exception 数据库调用异常
     * @return SingleResponse
     */
    @ExceptionHandler({SQLException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R<Void> handleSQLException(SQLException exception) {
        log.error("数据库调用异常 ex={}", exception.getMessage(), exception);
        return R.fail("数据库调用异常，请联系管理员处理");
    }

    /**
     * DataIntegrityViolationException Exception
     *
     * @param dataIntegrityViolationException 数据库约束异常
     * @return SingleResponse
     */
    @ExceptionHandler({DataIntegrityViolationException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R<Void> handleDataIntegrityViolationException(DataIntegrityViolationException dataIntegrityViolationException) {
        log.error("数据约束异常 ex={}", dataIntegrityViolationException.getMessage(), dataIntegrityViolationException);
        return R.fail("数据约束异常");
    }

    /**
     * AccessDeniedException
     *
     * @param exception the exception
     * @return SingleResponse
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public R<Void> handleAccessDeniedException(AccessDeniedException exception) {
        log.error("拒绝授权异常信息 ex={}", exception.getMessage());
        return R.fail("权限不足，不允许访问");
    }

    /**
     * validation Exception
     *
     * @return SingleResponse
     */
    @ExceptionHandler({BindException.class})
    @ResponseStatus(HttpStatus.OK)
    public R<Void> handleBodyValidException(BindException exception) {
        List<FieldError> fieldErrors = exception.getBindingResult()
                .getFieldErrors();
        String format = String.format("%s %s", fieldErrors.getFirst()
                .getField(), fieldErrors.getFirst()
                .getDefaultMessage());
        log.warn("参数错误 ex={}", format);
        return R.fail(fieldErrors.getFirst()
                .getDefaultMessage());
    }

    /**
     * 请求参数异常
     *
     * @return <Void>
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.OK)
    public R<Void> handleConstraintViolationException(ConstraintViolationException exception) {
        String fieldErrors = exception.getConstraintViolations()
                .stream()
                .findFirst()
                .get()
                .getMessageTemplate();
        log.error("参数错误 ex={}", exception.getMessage());
        return R.fail(fieldErrors);
    }


    /**
     * 参数类型不正确
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    public R<Void> methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception) {
        log.error("参数类型不匹配 ex={}", exception.getMessage());
        String fieldError = exception.getName();
        return R.fail(fieldError + "-类型不匹配");
    }

    /**
     * 避免 404 重定向到 /error 导致NPE ,ignore-url 需要配置对应端点
     *
     * @return SingleResponse
     */
    @DeleteMapping("/error")
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public R<Void> noHandlerFoundException() {
        return R.fail(HttpStatus.NOT_FOUND.getReasonPhrase());
    }

    /**
     * 保持和低版本请求路径不存在的行为一致
     * <p>
     * <a href="https://github.com/spring-projects/spring-boot/issues/38733">[Spring Boot
     * 3.2.0] 404 Not Found behavior #38733</a>
     *
     * @return SingleResponse
     */
    @ExceptionHandler({NoResourceFoundException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public R<Void> noResourceFoundException(NoResourceFoundException exception) {
        log.error("资源异常 404 {}", exception.getMessage(), exception);
        return R.fail(exception.getMessage());
    }

    /**
     * 业务异常
     *
     * @return SingleResponse
     */
    @ExceptionHandler({BizException.class})
    public R<Void> bizException(BizException exception) {
        log.warn("业务异常 ex={}", exception.getMessage());
        return R.fail(exception.getErrCode(), exception.getMessage());
    }

    /**
     * 系统异常
     * 一般可重试处理
     *
     * @return SingleResponse
     */
    @ExceptionHandler({SysException.class})
    public R<Void> sysException(SysException exception) {
        log.error("系统异常 ex={}", exception.getMessage(), exception);
        return R.fail(exception.getErrCode(), exception.getMessage());
    }

    /**
     * sse断开异常
     */
    @ExceptionHandler({AsyncRequestNotUsableException.class})
    public void asyncRequestNotUsableException(AsyncRequestNotUsableException exception) {
        log.error("sse异常 ex={}", exception.getMessage());
    }

}
